/*
 * Copyright 2006-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.martinimix.service.payment.provider.cybersource;

import java.util.Map;

import net.martinimix.domain.order.Order;
import net.martinimix.domain.payment.CreditCard;
import net.martinimix.domain.payment.CreditCardAuthorization;
import net.martinimix.service.payment.CreditCardTransactionFailedException;
import net.martinimix.service.payment.provider.CreditCardProcessor;
import net.martinimix.service.payment.provider.cybersource.context.CyberSourceServiceRequest;

/**
 * Provides a CyberSource based payment processor.  This payment processor uses
 * <a href="http://www.cybersource.com/">CyberSource</a> for order payment services
 * and follows the general workflow established by CybserSource
 * for requesting payment services.  
 * 
 * <p><strong>Workflow:</strong></p>
 *  
 * 	<ol> 
 * 		<li>The payment processor receives a request for payment services 
 * 			(typically a request for payment authorization).</li>
 * 		<li>A call is made to {@link #prepareAuthorizationRequest(CreditCard, Order)}
 * 			which returns a {@link CyberSourceServiceRequest} prepared by the
 * 			registered {@link CyberSourceRequestAdapter}.  By default, a
 * 			{@link SimpleCyberSourceRequestAdapter} is used but this may be configured
 * 			using ___.
 * 		 </li>
 * 		<li>A request for payment services is executed by calling 
 * 			{@link #runTransaction(CyberSourceServiceRequest)}.
 *		<li>The transaction reply is parsed by calling {@link #handleAuthorizationResponse(Map)},
 * 			which, by default, delegates to the registered {@link CyberSourceResponseAdapter}
 * 			to build a {@link CreditCardAuthorization}.
 * 		</li>
 * 		<li>A call is made to {@link #determineResponseDecision(CreditCardAuthorization)} to determine
 * 			if the <code>CreditCardAuthorization</code> is acceptable.  This decision is delegated by
 * 			default to a {@link SimpleAuthorizationDecisionManager}, which may be customized
 * 			by registering a different decision manager via
 * 			{@link #setAuthorizationDecisionManager(AuthorizationDecisionManager)}. 
 * 		</li>
 * 	</ol>
 * 
 * @author Scott Rossillo
 *
 */
public class CyberSourceCreditCardProcessor
		extends AbstractCyberSourcePaymentProcessor implements CreditCardProcessor {
	
	private AuthorizationDecisionManager authorizationDecisionManager;
	
	private CyberSourceRequestAdapter cyberSourceRequestAdapter;
	private CyberSourceResponseAdapter cyberSourceResponseAdapter;
	
	/**
	 * Creates a new CyberSource credit card processor.
	 */
	public CyberSourceCreditCardProcessor() {  
		authorizationDecisionManager = new SimpleAuthorizationDecisionManager();
		this.cyberSourceRequestAdapter = new SimpleCyberSourceRequestAdapter();

		cyberSourceResponseAdapter = new SimpleCyberSourceResponseAdapter();
	}
	
	/**
	 * Returns a CyberSource authorization request for the given credit card.
	 * 
	 * @param creditCard the <code>CreditCard</code> to authorize
	 * @param order the <code>Order</code> for which the given <code>creditCard</code> is being
	 * authorized as a payment method
	 * 
	 * @return a <code>CyberSourceServiceRequest</code> for payment authorization
	 */
	protected CyberSourceServiceRequest prepareAuthorizationRequest(CreditCard creditCard, Order order) {
		
		return cyberSourceRequestAdapter.createAuthorizationRequest(creditCard, order);
	}

	/**
	 * Authorizes the given credit card payment via CyberSource payment services.
	 * 
	 * This implementation calls {@link #prepareAuthorizationRequest(CreditCard, Order)} to
	 * create a <code>CybersourceRequest</code> request for authorization and passes
	 * the reply to {@link #handleAuthorizationResponse(Map, CreditCard)} to generate
	 * a <code>CreditCardAuthorization</code>.
	 * 
	 * @throws CreditCardTransactionFailedException if CyberSource payment service throws
	 * a <code>FaultException</code> or a <code>ClientException</code>
	 * 
	 * @see net.martinimix.service.payment.provider.CreditServiceProvider#authorizePayment(net.martinimix.domain.payment.CreditCard, net.martinimix.domain.order.Order)
	 */
	public final CreditCardAuthorization authorizePayment(CreditCard creditCard, Order order) {
		
		return authorizePayment(prepareAuthorizationRequest(creditCard, order));
	}
	
	public final CreditCardAuthorization authorizePayment(CyberSourceServiceRequest serviceRequest) {
		
		Map response = this.runTransaction(serviceRequest);
		
		return handleAuthorizationResponse(response);
	}
	
	/**
	 * Returns a credit card authorization from the given CyberSource reply message.
	 * 
	 * @param reply a <code>Map</code> containing the raw CyberSource response
	 * 
	 * @return a <code>CreditCardAuthorization</code> created from the given <code>reply</code>
	 */
	protected CreditCardAuthorization handleAuthorizationResponse(Map reply) {
		
		CreditCardAuthorization auth = cyberSourceResponseAdapter.parseAuthorizationResponse(reply);
		
		if(auth.getMerchantId() == null) {
			auth.setMerchantId(cyberSourceConfiguration.getMerchantId());
		}
		
		determineResponseDecision(auth);
		return auth;
	}
	
	/**
	 * Determines if the response decision for the given authorization.  The
	 * method delegates to a registered {@link AuthorizationDecisionManager} to
	 * decide if the response represents an acceptable authorization.
	 * 
	 * @param authorization the <code>CreditCardAuthorization</code> to examine
	 * 
	 * @see #setAuthorizationDecisionManager(AuthorizationDecisionManager)
	 */
	protected void determineResponseDecision(CreditCardAuthorization authorization) {
		
		authorization.setPositiveResponse(authorizationDecisionManager.acceptAuthorization(authorization));
		authorization.setStatusCode( authorization.isPositiveResponse() ? 
				CreditCardAuthorization.STATUS_SUCCESS : CreditCardAuthorization.STATUS_FAILED );
	}

	/**
	 * Sets the credit card authorization decision manager this credit card processor
	 * will use to determine if an authorization represents a positive authentication.
	 * 
	 * @param authorizationDecisionManager the <code>AuthorizationDecisionManager</code>
	 * this credit card processor will use to determine if an authorization is
	 * positive
	 */
	public void setAuthorizationDecisionManager(
			AuthorizationDecisionManager authorizationDecisionManager) {
		
		if(authorizationDecisionManager == null) {
			throw new IllegalArgumentException("[AuthorizationDecisionManager] cannot be null!");
		}
		
		this.authorizationDecisionManager = authorizationDecisionManager;
	}

	public void setCyberSourceRequestAdapter(
			CyberSourceRequestAdapter cyberSourceRequestAdapter) {
		this.cyberSourceRequestAdapter = cyberSourceRequestAdapter;
	}

	public void setCyberSourceResponseAdapter(
			CyberSourceResponseAdapter cyberSourceResponseAdapter) {
		this.cyberSourceResponseAdapter = cyberSourceResponseAdapter;
	}

}
